#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""A concrete barrier between segments of the program."""

namespace Grawlix.Services
import Grawlix.Application
import Grawlix.Meta
import C5
import ConcurrentBoo
import System
import System.Reflection

transient final class Domain(PermanentMarshalByRefObject):
"""An abstraction providing for communication between program boundaries."""
	
	def constructor(
		[required] name as string,
		[required] assemblyName as string,
		[required] game as Game,
		[required] constraints as Type*):
	"""
	Initializes a new domain.
	Param name: A name to call this domain.
	Param assemblyName: The name of the assembly to load from.
	Param game: The reference to the game object.
	Param constraints: The set of constraints to apply
	Remarks: This is an advanced function.
		Do not use unless designing your own IDomainFactory.
	"""
		_name = name
		_assemblyName = assemblyName
		_game = game
		_constraints.Add(typeof(IGovernor))
		for constraint in constraints:
			if not constraint.IsInterface:
				raise ArgumentException(
					"Unable to create a <Domain> with " +
					"non-interface constraint ${constraint}.",
					"constraints")
			_constraints.Add(constraint)
	
	def Activate():
	"""
	Starts loading the Domain.
	Raises InvalidOperationException: Called more than once.
	Remarks: This is an advanced function.
		Do not use unless designing your own IDomainFactory.
		Domains require two-step activation to
		avoid an obscure race condition.
	"""
		if _computation is not null:
			raise InvalidOperationException(
				"Domains should only be activated once, " +
				"and is usually taken care of automatically. " +
				"Make sure to not call this function extraneously.")
		_computation = Primitive.Future() do ():
			_governor = LoadGovernor(_assemblyName)
	
	private def LoadGovernor([required] assemblyName as string):
	"""
	Initializes any common services for this domain and load a governor.
	Param assemblyName: The name of the assembly to load.
	Returns: The newly constructed governor.
	Remarks: Call the default implementation to load the governor.
	"""
		assert _governor is null
		try:
			assembly = Assembly.Load(assemblyName)
		except exception:
			raise DomainLoadException(
				"Encountered an error attempting to load " +
				"an assembly (${assemblyName}).\n",
				exception)
		# TODO: Assembly attribute to describe controller name.
		if assembly != typeof(Game).Assembly:
			governorName = _defaultGovernorName
			governorType = assembly.GetType(governorName)
		else:
			governorType = typeof(BuiltInGovernor)
		if governorType is null:
			raise DomainLoadException(
				"Could not find user governor " +
				"(${governorType.FullName}) in " +
				"the user-provided assembly (${assemblyName}). " +
				"If the governor has a different name, " +
				"specify it with a GovernorNameAttribute")
		for constraint in _constraints:
			if not constraint.IsAssignableFrom(governorType):
				raise DomainLoadException(
					"The governor type (${governorType}) from " +
					"the user-provided assembly (${assemblyName}) does not " +
					"properly implement ${constraint}. " +
					"Ensure that the governor implements " +
					"the interfaces required for its use.")
		try:
			governor as IGovernor = governorType(_game)
		except exception:
			raise DomainLoadException(
				"Unable to construct an instance of " +
				"the governor (${governorType.FullName}) for " +
				"the user-provided assembly (${assemblyName}).\n",
				exception)
		return governor
	
	[lock(_sync)]
	def Post([required] message as Message):
	"""
	Serialize and post a message to the domain.
	Param message: The message to post.
	Remarks: Due to the appdomain boundary, it does not preserve
		object identity. (It makes a copy unless posted
		from the same AppDomain.)
	"""
		_eventQueue.Enqueue(message)
	
	[lock(_sync)]
	def Post([required] message as Message, expire as timespan):
	"""
	Serialize and post a message to the domain, after a set time.
	Param message: The message to post.
	Param expire: The duration after which to post the message.
	"""
		timed = TimedMessage(Message: message,
		                     Time: expire + _currentTime)
		_timedQueue.Add(timed)
	
	[lock(_sync)]
	private def GrabMessages(elapsed as timespan):
	"""
	Grab messages that have occurred in the given timespan
	and clear the queue.
	Param elapsed: Time since last update.
	Returns: The grabbed queue.
	"""
		grab = _eventQueue
		_eventQueue = LinkedList[of Message]()
		_currentTime += elapsed
		while (not _timedQueue.IsEmpty and
				_currentTime < _timedQueue.FindMin().Time):
			grab.Enqueue(_timedQueue.DeleteMin().Message)
		return grab
	
	def Update(elapsed as timespan):
	"""
	Update this domain.
	Param elapsed: Time since last update.
	Remarks: Default implementation pumps the message queue.
	"""
		if Ready:
			_governor.Update(elapsed)
			for message in GrabMessages(elapsed):
				Run(message)
	
	private def Run(message as Message):
	"""
	Defer a message to the controller.
	Param message: The message to run.
	"""
		assert Ready
#		log Debug, "Domain (${self}) sending message (${message}) " + \
#			"to governor (${_governor})."
		try:
			(_governor as duck).Run(message)
		except exception:
			log Warn, \
				"Exception received from " + \
				"the user governor (${_governor}) in " + \
				"domain ${Name} " + \
				"resulting from an attempt to process a message.\n" + \
				"${exception}"
	
	def DirectAccess[of T]():
	"""
	Directly accesses the governor of
	this domain using a configured interface.
	Raises InvalidOperationException: If Ready is false.
	Remarks: Any methods called directly on a governor have
		to follow a strict undocumented protocol.
		Do not use this method without understanding all the consequences.
	"""
		if not Ready:
			raise InvalidOperationException(
				"A Domain must be ready before its governor is queried.")
		if not _constraints.Contains(typeof(T)):
			raise ArgumentException(
				"The generic parameter (${T}) must be " +
				"one of the constraints supplied at Domain initialization.")
		governor = _governor as T
		assert governor is not null
		return governor
	
	override def ToString():
	"""
	Get a string representation of the object.
	Returns: The string representation.
	"""
		return "Domain ${Name}"
	
	Ready:
	"""
	A domain is initialized when the initialization
	procedure has completed.
	"""
		get:
			if _computation is null:
				return false
			return _computation.Finished
	
	Failure:
	"""
	Get the exception returned from a failed load operation, or
	null if the operation succeeded.
	"""
		get:
			if not Ready:
				raise InvalidOperationException(
					"Attempt to check on the failure result of " + 
					"a domain (${self}) " +
					"when it is not finished loading. " +
					"First check the Ready property to see " +
					"if the load is finished.")
			return _computation.Failure
	
	[getter(Name)]
	private _name as string
	"""A name for this Domain."""
	
	private _assemblyName as string
	"""A name for this Domain."""
	
	[getter(Game)]
	[IgnoreDisposal]
	private _game as Game
	"""The world this domain belongs to."""
	
	private _currentTime = timespan(0)
	"""The last known time since startup."""
	
	private _eventQueue = LinkedList[of Message]()
	"""The internal event queue."""
	
	private _timedQueue = IntervalHeap[of TimedMessage]()
	"""PriorityQueue for timed events."""
	
	private _sync = object()
	"""Synchronization object for queues."""
	
	private _governor as IGovernor
	"""The controller that this domain defers to."""
	
	private _computation as Computation
	"""The internal loading computation used."""
	
	private static final _defaultGovernorName = "Governor"
	"""
	A default name for governors if none
	is specified in assembly attributes.
	"""
	
	Constraints as IList[of Type]:
	"""The interfaces a controller must implement."""
		get:
			return GuardedList[of Type](_constraints)
	
	private _constraints = ArrayList[of Type]()
	"""The type of domain environment to spawn."""
	
internal struct TimedMessage(System.IComparable[of TimedMessage]):
"""A merging of a time and a message."""
	
	[Comparison]
	def Comparison(rhs as TimedMessage):
	"""
	Compare times.
	Param rhs: The other message to compare to.
	Returns: Positive if less than rhs,
		negative if greater than rhs, else 0.
	"""
		return Time.CompareTo(rhs.Time)
	
	[property(Time)]
	_time as timespan
	"""The time to expire."""
	
	[property(Message)]
	_message as Message
	"""The message to invoke."""


	